/**
 * std::future 和 std::packaeged_task 配合使用
 * std::packaged_task 是将 std::future对象和异步任务绑定在一起的模板
 */

#include <iostream>
#include <future>
#include <chrono>
#include <thread>
#include <memory>

int add(int num1, int num2)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return num1 + num2;
}


int main()
{
    /* 在一个线程中调用 std::packaged_task 包装的任务 */
    // std::packaged_task<int(int, int)> task(add);
    // std::future<int> return_future = task.get_future();

    // task(2, 3);

    // // 执行其他操作    
    // for(int i = 1; i < 11; i++)
    // {
    //     std::cout << i << " 主线程中其他操作执行中..." << std::endl;
    //     std::this_thread::sleep_for(std::chrono::seconds(1));
    // }

    // int ret = return_future.get();

    // std::cout << "异步任务的结果为: " << ret << std::endl;


    /**
     * 异步调用 std::packaged_task 包装的任务
     * 
     * 要异步调用 std::packaged_task 任务，就需要传参，C++中参数传递的方式有三种：1.传值 2.传地址 3.传引用
     * 
     * 1.传值：形参是实参的一份临时拷贝。
     * std::packaged_task是禁止拷贝的，因为std::packaged_task的设计初衷是单一结果接收者
     * 也就是说 std::packaged_task 和 std::future 之间是一对一绑定的，如果允许拷贝的话，会导致多个std::packaged_task对象
     * 关联同一个std::future，破坏其唯一性，当任务执行时就无法确定结果传递给哪个std::future对象，引发逻辑混乱。
     * 
     * 2.传地址：用对象的地址作为参数，通过解引用的方式访问变量
     * 优点：效率高
     * 缺点：复杂，面对多级指针的时候容易混乱
     * 
     * 3.传引用：形参就是实参
     * 优点：方便
     * 缺点：多线程任务下，可能存在 "局部变量的生命周期问题"
     * (也就是说，在一个线程中，局部变量因为生命周期结束而释放，另一个线程中引用该局部变量之后还使用，就会造成找不到局部变量的问题)
     * 
     * 所以：想要异步调用std::packaged_task对象，只能封装地址地址，进行解引用来调用，而类型不同的问题，可以使用类型推导来解决。
     */

    auto task_ptr = std::make_shared<std::packaged_task<int(int, int)>>(add);
    std::future<int> return_future = task_ptr->get_future();
    
    std::thread thread([task_ptr](){
        (*task_ptr)(1, 2);
    });

    thread.detach();

    // 执行其他操作
    for(int i = 1; i < 11; i++)
    {
        std::cout << i << " 主线程中其他操作执行中..." << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    int ret = return_future.get();
    std::cout << "异步任务执行的结果为: " << ret << std::endl;

    if(thread.joinable())
    {
        thread.join();
    }

    return 0;
}